Skip to content

feat(cli): port projects commands to native TypeScript#5392

Open
Coly010 wants to merge 6 commits into
developfrom
cli/port-projects-commands
Open

feat(cli): port projects commands to native TypeScript#5392
Coly010 wants to merge 6 commits into
developfrom
cli/port-projects-commands

Conversation

@Coly010
Copy link
Copy Markdown
Contributor

@Coly010 Coly010 commented May 29, 2026

What changed

Replaces the four Phase-0 Go proxies in the legacy projects command group with native TypeScript Effect implementations that call the Management API directly, and flips their rows in go-cli-porting-status.md from wrapped to ported.

  • listGET /v1/projects; soft linked-ref marker via the new resolveOptional (never prompts/fails); two-axis output (Go --output pretty|json|yaml|toml|env × TS --output-format text|json|stream-json), with --output env unsupported, matching Go.
  • create — interactive/non-interactive gating on --interactive + TTY + output mode; prompts for name/org/region/password when omitted; crypto-RNG 16-char password fallback; desired_instance_size only when --size set; --plan accepted-but-ignored (hidden, vestigial in Go); dashboard URL resolved per profile.
  • delete — arg-or-prompt ref resolution (never reads the linked file as a source); confirmation defaulting to No and honouring --yes; 404 → does-not-exist, other non-2xx → failed; best-effort supabase/.temp unlink when the deleted ref matches the linked one.
  • api-keys — ref resolution through the shared resolver (flag → env → linked file → prompt on a TTY → error); NAME | KEY VALUE table with ****** masking; SUPABASE_<NAME>_KEY env/toml encoding; raw ApiKeyResponse[] for json/yaml.

Shared infrastructure

  • LegacyProjectRefResolver gains resolveOptional (soft marker for list) and promptProjectRef(title) (per-command prompt label for delete); existing resolve unchanged.
  • Hoists apiKeysToEnv into legacy/shared/legacy-api-keys.format.ts and reuses the existing legacy-timestamp.format.ts instead of duplicating a timestamp formatter (per the hoist-before-duplicate policy).

Reviewer notes

  • Strict Go parity for stdout/stderr text, flags, exit codes, API routes, and error-message phrasing. Refs are validated against ^[a-z]{20}$ before reaching any API path.
  • Intentional divergences (documented in SIDE_EFFECTS.md): terminal ANSI color is omitted (Go disables color on non-TTY output, so piped output matches byte-for-byte); the non-interactive create error consolidates cobra's per-flag "required" errors into one message; DB_PASSWORD is not consumed (Go only mirrors --db-password into viper for local-stack reuse).
  • New unit + integration coverage across all four commands and the resolver; per-command and group-level SIDE_EFFECTS.md.

Closes CLI-1288.

Replace the four Phase-0 Go proxies in the `projects` group (`list`,
`create`, `delete`, `api-keys`) with native Effect implementations that hit
the Management API directly, and flip their porting-status rows to `ported`.

- list: GET /v1/projects + soft linked-ref marker (resolveOptional), two-axis
  output (Go --output pretty|json|yaml|toml|env x TS --output-format), env
  unsupported.
- create: interactive/non-interactive flag gating, org/region/name/password
  prompts, crypto-RNG password fallback, dashboard URL by profile.
- delete: arg-or-prompt ref resolution, confirmation (default No, honours
  --yes), 404/unexpected mapping, best-effort supabase/.temp unlink.
- api-keys: ref resolution via shared resolver, NAME/KEY VALUE table with
  masking, SUPABASE_<NAME>_KEY env/toml encoding.

Shared infra:
- LegacyProjectRefResolver gains resolveOptional + promptProjectRef(title).
- Hoist apiKeysToEnv into legacy/shared/legacy-api-keys.format.ts and reuse the
  existing legacy-timestamp.format.ts (drop duplicate formatter).

Adds unit + integration coverage and per-command + group SIDE_EFFECTS.md.
@Coly010 Coly010 requested a review from a team as a code owner May 29, 2026 12:15
@Coly010 Coly010 self-assigned this May 29, 2026
Coly010 added 5 commits May 29, 2026 13:44
The native port broke three `projects` parity dimensions the Go proxy passed:

- delete: reproduce Go's best-effort per-ref keyring delete
  (`credentials.StoreProvider.Delete`) — add `LegacyCredentials.deleteProjectCredential`,
  surfacing "Keyring is not supported on WSL" to stderr when the keyring is
  unsupported (matches `assertKeyringSupported`), swallowing a missing entry.
- create: send the POST body with alphabetically-sorted keys via
  `encodeGoStructJsonBody` over the raw HTTP client so it byte-matches Go's
  `json.Marshal`.
- list/create: parse the `/v1/projects` response via the raw HTTP client — the
  typed client's `ref: isMinLength(20)`/`^[a-z]+$` schema rejects the cli-e2e
  `__PROJECT_REF__` placeholder fixtures. Same workaround as `legacySuggestUpgrade`.
- list: print Go's `ErrNotLinked` stderr line ("Cannot find project ref...")
  when no project is linked, then render the table anyway.

Also caches the resolved ref on delete (PersistentPostRun parity), updates the
SIDE_EFFECTS notes, and adds integration coverage for the new branches.
… raw HTTP

Replace the hand-built `HttpClientRequest` calls with a typed escape hatch on
the platform API client so list/create reuse its URL/auth/header/body handling.

- packages/api: add `executeRaw(definition, input)` to the client — builds the
  request exactly like `execute` but returns the raw response without output
  decoding (callers parse leniently when the strict generated schema can't
  represent a value, e.g. the cli-e2e `__PROJECT_REF__` ref placeholder).
- packages/api: serialize JSON request bodies with alphabetically-sorted keys
  to match Go's `json.Marshal`, fixing multi-field body parity generally (e.g.
  `projects create --size`); previously only single/already-sorted bodies matched.
- projects list/create: use `api.executeRaw(...)` instead of manual requests.
- delete: correctly surface "Keyring is not supported on WSL" — the headless CI
  keyring failure is not the WSL osrelease path, so discriminate the
  `@napi-rs/keyring` "No matching credential found" (entry absent → swallow)
  from a missing backend (→ emit, matching Go's `ErrNotSupported`).
…e in e2e

Auditing Go's `delete.Run` against the port: Go best-effort deletes a per-ref
keyring credential, but Go only ever *stores* the profile-scoped access token in
the keyring (`StoreProvider.Set` is only ever called with `CurrentProfile.Name`,
never a project ref). So that delete always targets a non-existent entry — a
functional no-op for both CLIs. Its only observable output is Go's keyring
*availability* error ("Keyring is not supported on WSL"), emitted when the system
keyring backend is down (headless CI with no D-Bus). The TS `@napi-rs/keyring`
kernel-keyutils backend is always available and never hits this.

So the faithful port is to not perform the no-op delete, and to treat Go's
keyring-availability line as the environment noise it is:

- revert the per-ref `deleteProjectCredential` machinery (it could not match Go
  without faking Go's message); the delete handler keeps the API call, status
  mapping, `supabase/.temp` unlink, and output — all matching Go.
- normalize "Keyring is not supported on WSL" out of stderr in the cli-e2e
  parity harness, consistent with how it already treats keyring divergence
  (login/logout parity is deferred for the same reason).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant